﻿namespace Edge.Utilities
{
    using System;
    using System.Globalization;

    #region Public Enumerations

    /// <summary>
    /// Date interval unit types.
    /// </summary>
    public enum DateInterval
    {
        Day,
        DayOfYear,
        Hour,
        Minute,
        Month,
        Quarter,
        Second,
        Weekday,
        WeekOfYear,
        Year
    } 

    /// <summary>
    /// Describes the quarters of a year.
    /// </summary>
    public enum Quarter
    {
        First = 1,
        Second = 2,
        Third = 3,
        Fourth = 4
    }

    /// <summary>
    /// Describes the months of the year.
    /// </summary>
    public enum Month
    {
        January = 1,
        February = 2,
        March = 3,
        April = 4,
        May = 5,
        June = 6,
        July = 7,
        August = 8,
        September = 9,
        October = 10,
        November = 11,
        December = 12
    }

    #endregion

    /// <summary>
    /// Contains various date/time-related tools.
    /// </summary>
    public class DateTimeUtilities
    {
        #region Public Methods

        /// <summary>
        /// Retrieves the quarter the specified month resides in.
        /// </summary>
        /// <param name="month">Month to retrieve the quarter for.</param>
        /// <returns>The quarter the month resides in.</returns>
        public static int GetQuarter(int month)
        {
            if (month <= 3)
            {
                return 1;
            }

            if (month <= 6)
            {
                return 2;
            }

            if (month <= 9)
            {
                return 3;
            }

            return 4;
        }

        #region Quarters

        /// <summary>
        /// Gets the start of the specified quarter.
        /// </summary>
        /// <param name="year">Year to use when retrieving the date.</param>
        /// <param name="qtr">Quarter to use when retrieving the date.</param>
        /// <returns>The start of the specified quarter.</returns>
        public static DateTime GetStartOfQuarter(int year, Quarter qtr)
        {
            if (qtr == Quarter.First)
            {
                // 1st Quarter = January 1 to March 31
                return new DateTime(year, 1, 1, 0, 0, 0, 0);
            }
            else if (qtr == Quarter.Second)
            {
                // 2nd Quarter = April 1 to June 30
                return new DateTime(year, 4, 1, 0, 0, 0, 0);
            }
            else if (qtr == Quarter.Third)
            {
                // 3rd Quarter = July 1 to September 30
                return new DateTime(year, 7, 1, 0, 0, 0, 0);
            }
            else
            {
                // 4th Quarter = October 1 to December 31
                return new DateTime(year, 10, 1, 0, 0, 0, 0);
            }
        }

        /// <summary>
        /// Gets the end of the specified quarter.
        /// </summary>
        /// <param name="year">Year to use when retrieving the date.</param>
        /// <param name="qtr">Quarter to use when retrieving the date.</param>
        /// <returns>The end of the specified quarter.</returns>
        public static DateTime GetEndOfQuarter(int year, Quarter qtr)
        {
            if (qtr == Quarter.First)
            {
                // 1st Quarter = January 1 to March 31
                return new DateTime(year, 3, DateTime.DaysInMonth(year, 3), 23, 59, 59, 999);
            }
            else if (qtr == Quarter.Second)
            {
                // 2nd Quarter = April 1 to June 30
                return new DateTime(year, 6, DateTime.DaysInMonth(year, 6), 23, 59, 59, 999);
            }
            else if (qtr == Quarter.Third)
            {
                // 3rd Quarter = July 1 to September 30
                return new DateTime(year, 9, DateTime.DaysInMonth(year, 9), 23, 59, 59, 999);
            }
            else
            {
                // 4th Quarter = October 1 to December 31
                return new DateTime(year, 12, DateTime.DaysInMonth(year, 12), 23, 59, 59, 999);
            }
        }

        /// <summary>
        /// Gets the quarter the specified month resides in.
        /// </summary>
        /// <param name="month">Month for which to receive the quarter.</param>
        /// <returns>The quarter the specified month resides in.</returns>
        public static Quarter GetQuarter(Month month)
        {
            if (month <= Month.March)
            {
                // 1st Quarter = January 1 to March 31
                return Quarter.First;
            }
            else if ((month >= Month.April) && (month <= Month.June))
            {
                // 2nd Quarter = April 1 to June 30
                return Quarter.Second;
            }
            else if ((month >= Month.July) && (month <= Month.September))
            {
                // 3rd Quarter = July 1 to September 30
                return Quarter.Third;
            }
            else
            {
                // 4th Quarter = October 1 to December 31
                return Quarter.Fourth;
            }
        }

        /// <summary>
        /// Gets the end of the last quarter.
        /// </summary>
        /// <returns>The end of the last quarter.</returns>
        public static DateTime GetEndOfLastQuarter()
        {
            if ((Month)DateTime.Now.Month <= Month.March)
            {
                // go to last quarter of previous year
                return GetEndOfQuarter(DateTime.Now.Year - 1, Quarter.Fourth);
            }
            else
            {
                // return last quarter of current year
                return GetEndOfQuarter(DateTime.Now.Year, GetQuarter((Month)DateTime.Now.Month));
            }
        }

        /// <summary>
        /// Gets the start of the last quarter.
        /// </summary>
        /// <returns>The start of the last quarter.</returns>
        public static DateTime GetStartOfLastQuarter()
        {
            if ((Month)DateTime.Now.Month <= Month.March)
            {
                // go to last quarter of previous year
                return GetStartOfQuarter(DateTime.Now.Year - 1, Quarter.Fourth);
            }
            else
            {
                // return last quarter of current year
                return GetStartOfQuarter(DateTime.Now.Year, GetQuarter((Month)DateTime.Now.Month));
            }
        }

        /// <summary>
        /// Gets the start of the current quarter.
        /// </summary>
        /// <returns>The start of the current quarter.</returns>
        public static DateTime GetStartOfCurrentQuarter()
        {
            return GetStartOfQuarter(DateTime.Now.Year, GetQuarter((Month)DateTime.Now.Month));
        }

        /// <summary>
        /// Gets the end of the current quarter.
        /// </summary>
        /// <returns>The end of the current quarter.</returns>
        public static DateTime GetEndOfCurrentQuarter()
        {
            return GetEndOfQuarter(DateTime.Now.Year, GetQuarter((Month)DateTime.Now.Month));
        }

        #endregion

        #region Weeks

        /// <summary>
        /// Gets the start of the last week.
        /// </summary>
        /// <returns>The start of last week.</returns>
        public static DateTime GetStartOfLastWeek()
        {
            int daysToSubtract = (int)DateTime.Now.DayOfWeek + 7;

            DateTime dt = DateTime.Now.Subtract(System.TimeSpan.FromDays(daysToSubtract));

            return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, 0);
        }

        /// <summary>
        /// Gets the end of the last week.
        /// </summary>
        /// <returns>The end of last week.</returns>
        public static DateTime GetEndOfLastWeek()
        {
            DateTime dt = GetStartOfLastWeek().AddDays(6);

            return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59, 999);
        }

        /// <summary>
        /// Gets the start of the current week.
        /// </summary>
        /// <returns>The start of current week.</returns>
        public static DateTime GetStartOfCurrentWeek()
        {
            int daysToSubtract = (int)DateTime.Now.DayOfWeek;

            DateTime dt = DateTime.Now.Subtract(System.TimeSpan.FromDays(daysToSubtract));

            return new DateTime(dt.Year, dt.Month, dt.Day, 0, 0, 0, 0);
        }

        /// <summary>
        /// Gets the end of the current week.
        /// </summary>
        /// <returns>The end of current week.</returns>
        public static DateTime GetEndOfCurrentWeek()
        {
            DateTime dt = GetStartOfCurrentWeek().AddDays(6);

            return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59, 999);
        }
        #endregion

        #region Months

        /// <summary>
        /// Gets the start of the specified month.
        /// </summary>
        /// <param name="month">Month to get the start date for.</param>
        /// <param name="year">Year to get the start date for.</param>
        /// <returns>The start of the specified month.</returns>
        public static DateTime GetStartOfMonth(Month month, int year)
        {
            return new DateTime(year, (int)month, 1, 0, 0, 0, 0);
        }

        /// <summary>
        /// Gets the end of the specified month.
        /// </summary>
        /// <param name="month">Month to get the end date for.</param>
        /// <param name="year">Year to get the end date for.</param>
        /// <returns>The end of the specified month.</returns>
        public static DateTime GetEndOfMonth(Month month, int year)
        {
            return new DateTime(year, (int)month, DateTime.DaysInMonth(year, (int)month), 23, 59, 59, 999);
        }

        #endregion

        #region Years
        
        /// <summary>
        /// Returns a date representing the start of the year.
        /// </summary>
        /// <param name="year">Year to retrieve the start date for.</param>
        /// <returns>A date representing the start of the year.</returns>
        public static DateTime GetStartOfYear(int year)
        {
            return new DateTime(year, 1, 1, 0, 0, 0, 0);
        }

        /// <summary>
        /// Returns a date representing the end of the year.
        /// </summary>
        /// <param name="year">Year to retrieve the end date for.</param>
        /// <returns>A date representing the end of the year.</returns>
        public static DateTime GetEndOfYear(int year)
        {
            return new DateTime(year, 12, DateTime.DaysInMonth(year, 12), 23, 59, 59, 999);
        }

        /// <summary>
        /// Gets the start of last year.
        /// </summary>
        /// <returns>The start of last year.</returns>
        public static DateTime GetStartOfLastYear()
        {
            return GetStartOfYear(DateTime.Now.Year - 1);
        }
        
        /// <summary>
        /// Returns a date representing the end of last year.
        /// </summary>
        /// <returns>A date representing the end of last year.</returns>
        public static DateTime GetEndOfLastYear()
        {
            return GetEndOfYear(DateTime.Now.Year - 1);
        }
        
        /// <summary>
        /// Returns a date representing the start of the current year.
        /// </summary>
        /// <returns>A date representing the start of the year.</returns>
        public static DateTime GetStartOfCurrentYear()
        {
            return GetStartOfYear(DateTime.Now.Year);
        }

        /// <summary>
        /// Returns a date representing the end of the current year.
        /// </summary>
        /// <returns>A date representing the end of the year.</returns>
        public static DateTime GetEndOfCurrentYear()
        {
            return GetEndOfYear(DateTime.Now.Year);
        }

        #endregion                

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the difference between two dates by using the specified interval unit.
        /// </summary>
        /// <param name="interval">Date interval unit.</param>
        /// <param name="date1">First date to compare.</param>
        /// <param name="date2">Second date to compare.</param>
        /// <returns>Difference between the dates.</returns>
        private static long DateDiff(DateInterval interval, DateTime date1, DateTime date2)
        {
            if (interval == DateInterval.Year)
            {
                return date2.Year - date1.Year;
            }

            if (interval == DateInterval.Month)
            {
                return (date2.Month - date1.Month) + (12 * (date2.Year - date1.Year));
            }

            TimeSpan ts = date2 - date1;

            if (interval == DateInterval.Day || interval == DateInterval.DayOfYear)
            {
                return Round(ts.TotalDays);
            }

            if (interval == DateInterval.Hour)
            {
                return Round(ts.TotalHours);
            }

            if (interval == DateInterval.Minute)
            {
                return Round(ts.TotalMinutes);
            }

            if (interval == DateInterval.Second)
            {
                return Round(ts.TotalSeconds);
            }

            if (interval == DateInterval.Weekday)
            {
                return Round(ts.TotalDays / 7.0);
            }

            if (interval == DateInterval.WeekOfYear)
            {
                while (date2.DayOfWeek != DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek)
                {
                    date2 = date2.AddDays(-1);
                }

                while (date1.DayOfWeek != DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek)
                {
                    date1 = date1.AddDays(-1);
                }

                ts = date2 - date1;

                return Round(ts.TotalDays / 7.0);
            }

            if (interval == DateInterval.Quarter)
            {
                double date1Quarter = GetQuarter(date1.Month);
                double date2Quarter = GetQuarter(date2.Month);
                double date1Diff = date2Quarter - date1Quarter;
                double date2Diff = 4 * (date2.Year - date1.Year);

                return Round(date1Diff + date2Diff);
            }

            return 0;
        }
        
        /// <summary>
        /// Rounds a double value.
        /// </summary>
        /// <param name="value">Value to round.</param>
        /// <returns>The results of the rounding operation.</returns>
        private static long Round(double value)
        {
            if (value >= 0)
            {
                return (long)Math.Floor(value);
            }

            return (long)Math.Ceiling(value);
        }

        #endregion
    }
}
